//=============================================================================
//     _   _                               ____                _  _          
//    | | | |  __ _  _ __   _ __   _   _  / ___|  _ __   _ __ (_)| |_   ___ 
//    | |_| | / _` || '_ \ | '_ \ | | | | \___ \ | '_ \ | '__|| || __| / _ \
//    |  _  || (_| || |_) || |_) || |_| |  ___) || |_) || |   | || |_ |  __/
//    |_| |_| \__,_|| .__/ | .__/  \__, | |____/ | .__/ |_|   |_| \__| \___|
//                  |_|    |_|     |___/         |_|                         
//
//                     HappySprite - We make sprites happy
//
// Copyright (c) 2007 by Tank Monkey Games
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//=============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

namespace HappySprite
{
    public class Rotation
    {
        Transform3D parent;

        float angle;
        Vector3 axis;
        float pitch;
        float yaw;
        float roll;

        bool axisAngleInvalid;
        bool yawPitchRollInvalid;
        bool isKnownToBeIdentity;
        bool quaternionInvalid;

        Quaternion quaternion;

        public Rotation()
        {
            SetIdentityCore();
        }

        public Rotation(Quaternion quaternion)
        {
            this.quaternion = quaternion;
            axisAngleInvalid = true;
            yawPitchRollInvalid = true;
        }

        public Rotation(Vector3 axis, float angle)
        {
            this.angle = angle;
            this.axis = axis;
            quaternionInvalid = true;
            yawPitchRollInvalid = true;
        }

        public Rotation(float yaw, float pitch, float roll)
        {
            this.pitch = pitch;
            this.yaw = yaw;
            this.roll = roll;
            axisAngleInvalid = true;
            Quaternion.CreateFromYawPitchRoll(yaw, pitch, roll, out quaternion);
        }

        internal Rotation(Transform3D parent)
        {
            this.parent = parent;
            SetIdentityCore();
        }

        public float Angle
        {
            get
            {
                UpdateAxisAngle();
                return angle;
            }
            set
            {
                UpdateAxisAngle(); // Ensure Axis is correct
                angle = value;
                isKnownToBeIdentity = false;
                quaternionInvalid = true;
                yawPitchRollInvalid = true;
                if (parent != null) parent.OnRotationChanged();
            }
        }

        public Vector3 Axis
        {
            get
            {
                UpdateAxisAngle();
                return axis;
            }
            set
            {
                UpdateAxisAngle(); // Ensure Angle is correct
                axis = value;
                isKnownToBeIdentity = false;
                quaternionInvalid = true;
                yawPitchRollInvalid = true;
                if (parent != null) parent.OnRotationChanged();
            }
        }

        /// <summary>
        /// Gets or sets the angle of rotation, in radians, around the x-axis.
        /// </summary>
        /// <value>Angle of rotation, in radians, around the x-axis.</value>
        public float Pitch
        {
            get 
            {
                UpdateYawPitchRoll();
                return pitch;
            }
            set
            {
                UpdateYawPitchRoll(); // Ensure other angles are valid
                pitch = value;
                Quaternion.CreateFromYawPitchRoll(yaw, pitch, roll, out quaternion);
                isKnownToBeIdentity = false;
                quaternionInvalid = false;
                axisAngleInvalid = true;
            }
        }

        /// <summary>
        /// Gets or sets the angle of rotation, in radians, around the y-axis.
        /// </summary>
        /// <value>Angle of rotation, in radians, around the y-axis.</value>
        public float Yaw
        {
            get 
            {
                UpdateYawPitchRoll();
                return yaw;
            }
            set
            {
                UpdateYawPitchRoll(); // Ensure other angles are valid
                yaw = value;
                Quaternion.CreateFromYawPitchRoll(yaw, pitch, roll, out quaternion);
                isKnownToBeIdentity = false;
                quaternionInvalid = false;
                axisAngleInvalid = true;
            }
        }

        /// <summary>
        /// Gets or sets the angle of rotation, in radians, around the z-axis.
        /// </summary>
        /// <value>Angle of rotation, in radians, around the z-axis.</value>
        public float Roll
        {
            get 
            {
                UpdateYawPitchRoll();
                return roll;
            }
            set
            {
                UpdateYawPitchRoll(); // Ensure other angles are valid
                roll = value;
                Quaternion.CreateFromYawPitchRoll(yaw, pitch, roll, out quaternion);
                isKnownToBeIdentity = false;
                quaternionInvalid = false; 
                axisAngleInvalid = true;
            }
        }

        public Quaternion Quaternion
        {
            get 
            {
                UpdateQuaternion();
                return quaternion; 
            }
            set 
            {
                SetRotation(value);
                if (parent != null) parent.OnRotationChanged();
            }
        }

        public void Rotate(Rotation rotation)
        {
            Quaternion *= rotation.Quaternion;
        }

        public void Rotate(Quaternion quaternion)
        {
            Quaternion *= quaternion;
        }

        public void Rotate(Vector3 axis, float angle)
        {
            Quaternion *= Quaternion.CreateFromAxisAngle(axis, angle);
        }

        public void RotateYaw(float angle)
        {
            Quaternion *= Quaternion.CreateFromAxisAngle(Vector3.UnitY, angle);
        }

        public void RotatePitch(float angle)
        {
            Quaternion *= Quaternion.CreateFromAxisAngle(Vector3.UnitX, angle);
        }

        public void RotateRoll(float angle)
        {
            Quaternion *= Quaternion.CreateFromAxisAngle(Vector3.UnitZ, angle);
        }

        public void SetIdentity()
        {
            SetIdentityCore();
            if (parent != null) parent.OnRotationChanged();
        }

        internal void SetIdentityCore()
        {
            quaternion = Quaternion.Identity;
            axis = Vector3.UnitZ;
            angle = 0f;
            pitch = 0f;
            yaw = 0f;
            roll = 0f;
            isKnownToBeIdentity = true;
            quaternionInvalid = false;
            axisAngleInvalid = false;
            yawPitchRollInvalid = false;
        }

        internal bool IsKnownToBeIdentity()
        {
            return isKnownToBeIdentity;
        }

        internal void SetRotation(Quaternion value)
        {
            quaternion = value;
            isKnownToBeIdentity = false;
            quaternionInvalid = false;
            axisAngleInvalid = true;
            yawPitchRollInvalid = true;
        }

        void UpdateAxisAngle()
        {
            if (axisAngleInvalid)
            {
                QuaternionExtensions.ToAxisAngle(ref quaternion, out axis, out angle);
                axisAngleInvalid = false;
            }
        }

        void UpdateYawPitchRoll()
        {
            if (yawPitchRollInvalid)
            {
                UpdateQuaternion();
                QuaternionExtensions.ToYawPitchRoll(ref quaternion, out yaw, out pitch, out roll); 
                yawPitchRollInvalid = false;
            }
        }

        void UpdateQuaternion()
        {
            if (quaternionInvalid)
            {
                Quaternion.CreateFromAxisAngle(ref axis, angle, out quaternion);
                quaternionInvalid = false;
            }
        }
    }
}
